package scuff.msg.json.gson

import scuff.DoubleDispatch
import scuff.msg._
import scuff.msg.util._
import scuff.msg.json._
import scuff.msg.json.gson._

import org.junit._
import org.junit.Assert._

import com.google.gson._

class TestGson {

  var reader: ReflectiveGsonReader[TestCallback, TestMessage] = _
  var writer: ReflectiveGsonWriter[TestMessage] = _
  val myWriter = FasterJsonWriter

  val callback = new TestCallback {
    var lastBid, lastAsk = Float.NaN
    var lastTicker: String = _
    var lastHalted = false
    var isShutdown = false
    def onMessage(msg: StockQuote) {
      lastBid = msg.bid
      lastAsk = msg.ask
      lastTicker = msg.ticker
    }
    def onMessage(msg: StockStatus) {
      lastHalted = msg.halted
      lastTicker = msg.ticker
    }
    def onMessage(msg: Shutdown) = isShutdown = true
  }

  @Before
  def setup() {
    reader = new ReflectiveGsonReader[TestCallback, TestMessage] with IdAsClassName[TestMessage] {
      val basePackage = getClass.getPackage
      def callback = TestGson.this.callback
    }
    writer = new ReflectiveGsonWriter[TestMessage] with ClassNameAsType
  }

  @Test
  def marshal() {
    val quote = new StockQuote("MSFT", 23.21f, 23.24f)
    print("Gson: ")
    writer.write(quote)(System.out)
    println
  }

  @Test
  def marshalHallow() {
    print("Gson: ")
    writer.write(new Shutdown)(System.out)
    println
  }

  @Test
  def quote() {
    val json = """
{
      'id': 'StockQuote',
      'payload': {
          'ticker': "MSFT",
          'ask': 42.51,
          'bid': 42.48
      }
}
"""
    assertTrue(callback.lastAsk.isNaN)
    assertTrue(callback.lastBid.isNaN)
    assertNull(callback.lastTicker)
    reader.read(json)
    assertEquals("MSFT", callback.lastTicker)
    assertEquals(42.51f, callback.lastAsk, 0.0f)
    assertEquals(42.48f, callback.lastBid, 0.0f)
  }

  @Test
  def halted() {
    val json = """
{
      'id': 'StockStatus',
      'payload': {
          'ticker': "SIRI",
          'halted': true
      }
}
"""
    assertFalse(callback.lastHalted)
    assertNull(callback.lastTicker)
    reader.read(json)
    assertEquals("SIRI", callback.lastTicker)
    assertTrue(callback.lastHalted)
  }

  @Test
  def noPayload() {
    val json = "{ 'id': 'Shutdown' }"
    assertFalse(callback.isShutdown)
    reader.read(json)
    assertTrue(callback.isShutdown)
  }

  @Test
  def faster() {
    val quote = new StockQuote("FOO", 12.34f, 12.35f)
    val expected = """{"id":"StockQuote","payload":{"ticker":"FOO","bid":12.34,"ask":12.35}""".getBytes
    val out = new java.io.ByteArrayOutputStream(512)
    myWriter.write(quote)(out)
    assertTrue(java.util.Arrays.equals(expected, out.toByteArray))
  }
}

trait TestCallback {
  def onMessage(msg: StockQuote)
  def onMessage(msg: StockStatus)
  def onMessage(msg: Shutdown)
}

abstract class TestMessage extends DoubleDispatch[TestCallback] {
  @transient val hasContent = scuff.Surgeon.hasSerializableFields(getClass)
  def writeThyself(out: java.io.OutputStream)
}

class StockQuote(val ticker: String, val bid: Float, val ask: Float) extends TestMessage {
  def dispatch(cb: TestCallback) = cb.onMessage(this)
  def writeThyself(out: java.io.OutputStream) {
    out.write("\"ticker\":\"".getBytes); out.write(ticker.getBytes); out.write("\",".getBytes)
    out.write("\"bid\":".getBytes); out.write(bid.toString.getBytes); out.write(",".getBytes)
    out.write("\"ask\":".getBytes); out.write(ask.toString.getBytes);
  }
}

class Shutdown extends TestMessage {
  def dispatch(cb: TestCallback) = cb.onMessage(this)
  @Override
  def writeThyself(out: java.io.OutputStream) = throw new UnsupportedOperationException("Nothing to write. Check 'hasContent'.")
}

class StockStatus(val ticker: String, val halted: Boolean) extends TestMessage {
  def dispatch(cb: TestCallback) = cb.onMessage(this)
  def writeThyself(out: java.io.OutputStream) {
    out.write("\"ticker\":\"".getBytes); out.write(ticker.getBytes); out.write("\",".getBytes)
    out.write("\"halted\":".getBytes); out.write(halted.toString.getBytes);
  }
}

abstract class HasType extends Typed[String] {
  def id = "TheID"
}

object TestWriter extends ReflectiveGsonWriter[HasType] with OutgoingTypeChecker[String, HasType]

object FasterJsonWriter extends JsonMessageWriter[TestMessage, java.io.OutputStream] with ClassNameAsType {
  protected def serialize(message: OutgoingMessage[TestMessage], sink: java.io.OutputStream) {
    sink.write("{\"id\":\"".getBytes); sink.write(message.id.getBytes);
    if (message.payload.hasContent) {
      sink write ("\",\"payload\":{".getBytes)
      message.payload.writeThyself(sink)
      sink.write('}')
    }
  }
}